<?php

namespace hamster\tools\com;

// openssl 实现的 AES 加密类
class Aes
{
	private static $key = null; // 口令
	private static $iv = null; // 初始化向量
	private static $options = null; // 填充方式

	/**
	 * Aes constructor.
	 * @param string $key
	 * @param string $iv
	 * @param int $options
	 * 		OPENSSL_RAW_DATA: 自动对明文进行 padding, 但返回的结果未经过 base64 编码
	 * 		OPENSSL_NO_PADDING: 非填充模式
	 * 		OPENSSL_ZERO_PADDING: 自动对明文进行 0 填充, 返回的结果经过 base64 编码. 但是, openssl 不推荐 0 填充的方式, 即使选择此项也不会自动进行 padding, 仍需手动 padding
	 */
	public function __construct($key='', $iv='', $options = OPENSSL_NO_PADDING)
	{
		self::$key = $key;
		self::$iv = $iv;
		self::$options = $options;
	}

	/**
	 * 加密
	 * @param string $data 未加密数据
	 * @param string $mode 加密方式：ECB/CBC/OFB/CFB/CTR
	 * @param int $length 长度：128/192/256
	 * @return false|string
	 * error:ECB/CBC在OPENSSL_ZERO_PADDING会返回false，原因未知
	 */
	public function encrypt($data, $mode='CBC', $length=128)
	{
		$key = self::$key;
		$iv = self::$iv;
		if ($mode == 'ECB') $iv = '';

		$method = $this->_getMethod($mode, $length);

		$data = openssl_encrypt($data, $method, $key, self::$options, $iv);

		return $data;
	}

	/**
	 * 解密
	 * @param string $encrypt_data 已加密数据
	 * @param string $mode 加密方式：ECB/CBC/OFB/CFB/CTR
	 * @param int $length 长度：128/192/256
	 * @return string
	 */
	public function decrypt($encrypt_data, $mode='CBC', $length=128)
	{
		$key = self::$key;
		$iv = self::$iv;
		if ($mode == 'ECB') $iv = '';

		$method = $this->_getMethod($mode, $length);

		$data = openssl_decrypt($encrypt_data, $method, $key, self::$options, $iv);

		return trim($data); // （前端加密的数据有时解密后面会有一些空格）
	}

	private function _getMethod($mode, $length)
	{
		switch ($length) {
			default:
			case 128:
				$method = 'AES-128-';
				break;
			case 192:
				$method = 'AES-192-';
				break;
			case 256:
				$method = 'AES-256-';
				break;
		}
		switch ($mode) {
			default:
			case 'CBC': // 密码分组链模式
				$method .= 'CBC';
				// 先将明文切分成若干小段，然后每一小段与初始块或者上一段的密文段进行异或运算后，再与密钥进行加密。
				// 优点：能掩盖明文结构信息，保证相同密文可得不同明文，所以不容易主动攻击，安全性好于ECB，适合传输长度长的报文，是SSL和IPSec的标准。
				// 缺点：（1）不利于并行计算；（2）传递误差——前一个出错则后续全错；（3）第一个明文块需要与一个初始化向量IV进行抑或，初始化向量IV的选取比较复杂。
				// 初始化IV的选取方式：固定IV，计数器IV，随机IV（只能得到伪随机数，用的最多），瞬时IV（难以得到瞬时值）
				break;
			case 'ECB': // 电码本模式
				$method .= 'ECB';
				// 将整个明文分成若干段相同的小段，然后对每一小段进行加密。
				// 优：操作简单，易于实现；分组独立，易于并行；误差不会被传送。——简单，可并行，不传送误差。
				// 缺：掩盖不了明文结构信息，难以抵抗统计分析攻击。——可对明文进行主动攻击。
				break;
			case 'OFB': // 输出反馈模式
				$method .= 'OFB';
				// 密码算法的输出（指密码key而不是密文）会反馈到密码算法的输入中，OFB模式并不是通过密码算法对明文直接加密，而是通过将明文分组和密码算法的输出进行XOR来产生密文分组。
				// 优点：隐藏了明文模式；结合了分组加密和流密码（分组密码转化为流模式）；可以及时加密传送小于分组的数据。
				// 缺点：不利于并行计算；需要生成秘钥流；对明文的主动攻击是可能的。
				break;
			case 'CFB': // 密码反馈模式
				$method .= 'CFB';
				// 有点：不容易主动攻击（误差传递），分组转变为流模式，可加密小于分组数据
				// 缺点：无法并行、误差传递
				break;
			case 'CTR': // 计算器模式
				$method .= 'CTR';
				// 完全的流模式。将瞬时值与计数器连接起来，然后对此进行加密产生密钥流的一个密钥块，再进行XOR操作 。
				// 优点：不泄露明文；仅需实现加密函数；无需填充；可并行计算。
				// 缺点：需要瞬时值IV，难以保证IV的唯一性。
				break;
		}

		return $method;
	}

}